﻿using ArtMath.Core.Data;
using ArtMath.Core.Helper;
using System;
using System.Collections.Generic;

namespace ArtMath.PointCollection
{
    [Serializable]
    public class Ray : ContinuityPoint
    {
        public PointD SourcePoint;
        public double DirectedAngle;
        public Ray()
        {

        }
        public Ray(PointD src, double angle)
        {
            SourcePoint = src;
            DirectedAngle = angle;
        }
        public override bool Contains(PointD pt)
        {
            return Utility.ScalarProduct(Utility.GetUnitVector(DirectedAngle), pt - SourcePoint) >= 0 &&
                pt.DistanceTo(SourcePoint, Math.Tan(DirectedAngle)).AlmostEqual(0) &&
                AfterContains(pt);
        }
        public override PointD? GetNearestPoint(PointD pt)
        {
            if (Utility.ScalarProduct(Utility.GetUnitVector(DirectedAngle), pt - SourcePoint) < 0) return SourcePoint;
            return pt.GetProjectivePoint(SourcePoint, Math.Tan(DirectedAngle));
        }
        public override PointD? HeadPoint
        {
            get { return SourcePoint; }
        }
        public override double GetSlideExcess(PointD currentLocation, double length)
        {
            if (length < 0)
            {
                return -length - currentLocation.DistanceTo(SourcePoint);
            }
            return base.GetSlideExcess(currentLocation, length);
        }
        public override PointD SlideDestination(PointD currentLocation, double length, out bool reverse)
        {
            reverse = false;
            if (length > 0)
            {
                return currentLocation.Offset(DirectedAngle, length);
            }
            else
            {
                double excess = -length - currentLocation.DistanceTo(SourcePoint);
                if (excess <= 0)
                {
                    return SourcePoint.Offset(DirectedAngle, -excess);
                }
                else
                {
                    reverse = true;
                    return SourcePoint.Offset(DirectedAngle, excess);
                }
            }
        }
        protected override PointCollectionBase Intersect(PointCollectionBase collection)
        {
            HashSet<PointD> set = new HashSet<PointD>();
            Ray ray = collection as Ray;
            if (ray != null)
            {
                set.AddRange(CurveIntersection.RaysIntersection(SourcePoint, DirectedAngle, ray.SourcePoint, ray.DirectedAngle));
            }
            else
            {
                StraightLine sl = collection as StraightLine;
                if (sl != null)
                {
                    set.AddRange(CurveIntersection.RayStraightLineIntersection(SourcePoint, DirectedAngle, sl.Location, sl.Slope));
                }
                else
                {
                    PartialCircle arc = collection as PartialCircle;
                    if (arc != null)
                    {
                        set.AddRange(CurveIntersection.RayArcIntersection(SourcePoint, DirectedAngle, arc.Center, arc.Radius, arc.Start, arc.Sweep));
                    }
                    else
                    {
                        Circle cir = collection as Circle;
                        if (cir != null)
                        {
                            set.AddRange(CurveIntersection.RayCircleIntersection(SourcePoint, DirectedAngle, cir.Center, cir.Radius));
                        }
                        else
                        {
                            Ellipse oval = collection as Ellipse;
                            if (oval != null)
                            {
                                set.AddRange(CurveIntersection.RayEllipseIntersection(SourcePoint, DirectedAngle, oval.Center, oval.LongAxisHalfLength, oval.ShortAxisHalfLength, oval.RotateAngle));
                            }
                            else
                            {
                                Hyperbola hyp = collection as Hyperbola;
                                if (hyp != null)
                                {
                                    set.AddRange(CurveIntersection.RayHyperbolaIntersection(SourcePoint, DirectedAngle, hyp.Center, hyp.RealAxisHalfLength, hyp.ImaginaryAxisHalfLength, hyp.RotateAngle));
                                }
                                else
                                {
                                    Parabola para = collection as Parabola;
                                    if (para != null)
                                    {
                                        set.AddRange(CurveIntersection.RayParabolaIntersection(SourcePoint, DirectedAngle, para.Center, para.FocusDirectrixDistance, para.RotateAngle));
                                    }
                                    else
                                    {
                                        CurveDescribable cd = collection as CurveDescribable;
                                        if (cd != null && cd.Curves != null)
                                        {
                                            foreach (Curve item in cd.Curves)
                                            {
                                                set.AddRange(CurveIntersection.RayCurveIntersection(SourcePoint, DirectedAngle, item, item.IsClosed));
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            if (set.Count == 0) return Empty.Instance;
            return AfterIntersect(collection.AfterIntersect(new DiscontinuityPoint(set)));
        }
    }
}
